package robot

import (
	"bytes"
	"context"
	"encoding/base64"
	"image"
	_ "image/png"
	"math"
	"path"
	"strings"
	"time"

	"device-admin/config/mysql"
	"device-admin/config/redis"
	"device-admin/internal/app/grpc/protoc/robotc"
	"device-admin/internal/app/grpc/service/pubsub"
	"device-admin/internal/app/web/service/cmd"
	"device-admin/internal/app/web/service/device_map"
	"device-admin/internal/dal"
	"device-admin/internal/dao"
	"device-admin/internal/define"
	"device-admin/internal/models"

	"192.168.1.75/go-pkg/errorx"
	"192.168.1.75/go-pkg/go-cache"
	"192.168.1.75/go-pkg/helper"
	"192.168.1.75/go-pkg/logx"
	"google.golang.org/protobuf/proto"
)

// NotifyMapData2Server 地图数据变化通知
// 目前仅在地图修改时使用，定时上报是通过wilgmsg处理
func (s *Server) NotifyMapData2Server(ctx context.Context, data *robotc.NotifyMapData) (*robotc.RspMapData, error) {
	notifyMapDataHandle(ctx, data)
	return &robotc.RspMapData{NVoid: 1}, nil
}

// notifyMapDataHandle 上报地图信息
// 包含获取变幻矩阵即算子，用于绘制机器人路径，包含地图数据
func notifyMapDataHandle(ctx context.Context, mapData *robotc.NotifyMapData) {
	spanCtx := logx.Start(ctx, "NotifyMapData2Server")
	defer logx.End(spanCtx)
	robotID := GetRobotID(ctx)

	deviceMap := models.DeviceMap{}
	deviceMap.RobotID = robotID

	// 地图名称
	deviceMap.MapName = string(mapData.GetArrMap().GetBasedata().GetName())

	// 地图内容
	mapPngData := mapData.GetBkpicData()

	// 获取地图相关详细数据
	mapEx := mapData.GetArrMap()
	mapExBytes, err := proto.Marshal(mapEx)
	if err != nil {
		logx.Error(context.Background(), "NotifyMapData2Server", logx.String("mapEx Marshal error", err.Error()))
		return
	}
	mapExtData := mapExBytes
	deviceMap.MapExMd5 = helper.Md5Bytes(mapExBytes)

	// 变换矩阵
	affineData := mapData.GetArrMap().GetBasedata().GetAffinedata()

	/*计算地图的实际宽度和高度*/
	{
		imgBuf := bytes.NewBuffer(mapData.GetBkpicData())
		imgCfg, _, err := image.DecodeConfig(imgBuf)
		if err != nil {
			logx.Error(spanCtx, "decode map img error")
			return
		}
		temp := 1 / (float64(affineData.R12))
		// 地图宽度
		deviceMap.Width = math.Abs(float64(imgCfg.Width) * temp)
		// 地图高度
		deviceMap.Height = math.Abs(float64(imgCfg.Height) * temp)
		deviceMap.MapMd5 = helper.Md5Bytes(mapPngData)
		// 转换矩阵
		deviceMap.R11 = affineData.GetR11()
		deviceMap.R12 = affineData.GetR12()
		deviceMap.R21 = affineData.GetR21()
		deviceMap.R22 = affineData.GetR22()
		deviceMap.Tx = affineData.GetTx()
		deviceMap.Ty = affineData.GetTy()
	}

	// 同步地图
	device_map.SyncMapInfoLogic([]models.DeviceMap{deviceMap}, mapPngData, mapExtData)
	// 缓存任务模式
	// 先根据地图名称做判断,未来proto会提供专门的类型字段
	{
		if strings.Contains(strings.ToLower(deviceMap.MapName), "charging") {
			cache.HSet(define.CacheRobotRTInfo+robotID, "work_type", 2, 0, nil)
		} else if strings.Contains(deviceMap.MapName, "运营") {
			cache.HSet(define.CacheRobotRTInfo+robotID, "work_type", 1, 0, nil)
		} else {
			cache.HSet(define.CacheRobotRTInfo+robotID, "work_type", 3, 0, nil)
		}
	}
	// 缓存正在使用的地图的变换矩阵及地图图片内容
	// 并在地图同步时，清理掉无用地图关联数据
	{
		cache.HSet(define.CacheRobotRTInfo+robotID, "current_map_src_"+deviceMap.MapName, path.Join("/file/device-admin/device-map", robotID, helper.Md5Bytes(mapData.GetBkpicData())+".png"), 0, nil)
		dataBytes, _ := proto.Marshal(affineData)
		cache.HSet(define.CacheRobotRTInfo+robotID, deviceMap.MapName+"_affine_data", dataBytes, 0, nil)
	}
}

// GetCurrentPosition2Server 获取当前的位置
func (s *Server) GetCurrentPosition2Server(ctx context.Context, data *robotc.NotifyCurrentPosition) (*robotc.RspCurrentPosition, error) {
	return &robotc.RspCurrentPosition{NVoid: 0}, nil
}

// taskRecordHandle 任务记录处理
// 其中包含一般任务以及充电任务，暂时不考虑充电任务
func taskRecordHandle(ctx context.Context, data []byte) {
	robotID := GetRobotID(ctx)
	if robotID == "" {
		return
	}

	// 获取任务数据，并保存
	taskRecord := &robotc.RspGetTaskRecordInfoClient2Server{}
	err := proto.Unmarshal(data, taskRecord)
	if err != nil {
		return
	}
	// 获取数据
	// 空任务记录不处理
	taskInfos := taskRecord.GetData()
	if len(taskInfos) == 0 {
		return
	}

	// 保存设备运行数据的更新时间
	func() {
		redisCtx, redisCancel := context.WithTimeout(context.Background(), time.Second*3)
		defer redisCancel()
		redis.NewDB().HSet(redisCtx, define.RedisTaskDataUpdatedAt, robotID, time.Now().Unix())
	}()

	taskRecords := []*models.TaskRecord{}
	for _, taskInfo := range taskInfos {
		taskRecord := &models.TaskRecord{}
		taskRecord.TaskID = string(taskInfo.GetTaskID())
		taskRecord.RobotID = robotID
		taskRecord.TaskName = string(taskInfo.GetPathName())
		taskRecord.TaskType = int(taskInfo.GetTaskType())
		taskRecord.BeginTime = taskInfo.GetStartTimestamp()
		taskRecord.EndTime = taskInfo.GetEndTimestamp()
		taskRecord.Odometer = taskInfo.GetActualOdometer()
		taskRecord.MapName = string(taskInfo.GetOneRoute())
		taskRecord.TimeDuration = taskInfo.TimeConsuming
		taskRecord.CreatedMonth = time.Unix(taskInfo.GetStartTimestamp()/1000, 0).Format("2006-01")
		taskRecord.CreatedDate = time.Unix(taskInfo.GetStartTimestamp()/1000, 0).Format("2006-01-02")
		taskRecord.CurrentIndex = int64(taskInfo.GetCurrIndex())
		taskRecord.TotalIndex = int64(taskInfo.TotalIndex)
		taskRecord.User = string(taskInfo.GetUser())
		taskRecord.WorkType = int(taskInfo.GetTaskWorkType())
		taskRecords = append(taskRecords, taskRecord)
	}
	// 98声音恢复
	device_map.ResumeRobotVolume(ctx, robotID, taskRecords)
	// 新增任务记录
	dao.TaskRecord{}.Add(ctx, dal.Q, taskRecords)
	// 统计活跃数据
	date := time.Unix(taskInfos[0].GetStartTimestamp()/1000, 0).Format("2006-1-2")
	dao.DeviceActiveDuration{}.UpdateTaskActiveDuration(ctx, dal.Q, robotID, date)
}

// SetRunningMapCMDPack 选择地图命令封装
func SetRunningMapCMDPack(robotID string, mapName string, username string) error {
	// 参数检查
	if robotID == "" {
		return errorx.New("设备id不能为空", -1)
	}
	if mapName == "" {
		return errorx.New("地图名称不能为空", -1)
	}
	// 检查地图是否存在
	var count int64
	mysql.NewDB().Model(&models.DeviceMap{}).
		Where("robot_id = ?", robotID).
		Where("map_name = ?", mapName).
		Where("deleted_at = ?", 0).
		Count(&count)
	if count <= 0 {
		return errorx.New("地图不存在", -1)
	}

	setRuningMapCMD := &robotc.SetRuningMapCMD{
		StrMapName: []byte(mapName),
	}
	// 额外数据
	exData := &robotc.CmdExtend{}
	exData.LlTimeStamp = time.Now().UnixMilli()
	exData.StrUserName = []byte(username)
	setRuningMapCMD.ExData = exData
	dataBytes, err := proto.Marshal(setRuningMapCMD)
	if err != nil {
		logx.Error(context.Background(), "setRuningMapCMD", logx.Err(err))
	}
	err = pubsub.CMDPublish(robotID, "95", "setRuningMapCMD", base64.StdEncoding.EncodeToString(dataBytes))
	if err != nil {
		return err
	}

	// 等待命令执行结果
	resData, err := cmd.WaitCmdRsp(robotID, "setRuningMapCMD", "0", "95")
	if err != nil {
		return err
	}
	// 解析响应的内容
	setRuningMapRsp := &robotc.RspSetRuningMap2ClientC2S{}
	if err := proto.Unmarshal(resData, setRuningMapRsp); err == nil {
		resCode := int(setRuningMapRsp.GetEnStatus())
		if resCode == 0 {
			return errorx.New("未知错误", -1)
		} else if resCode == 1 {
			return nil
		} else if resCode == 2 {
			return errorx.New("地图已经在使用", -1)
		}
	}
	return nil
}

// SetRunningMap2Client 设置某个地图为使用
func (s *Server) SetRunningMap2Client(param *robotc.SetRuningMapCMDParam, sess robotc.BaseService_SetRunningMap2ClientServer) error {
	robotID := GetRobotID(sess.Context())
	chanMapKey := "setRuningMapCMD@" + robotID + "@95"
	pubsub.RobotCMDChanMap.Store(chanMapKey, make(chan string, 1))
	defer pubsub.RobotCMDChanMap.Delete(chanMapKey)
	robotCMDChanInterface, _ := pubsub.RobotCMDChanMap.Load(chanMapKey)
	robotCMDChan, _ := robotCMDChanInterface.(chan string)
	// 订阅对应主题
	for {
		select {
		case <-sess.Context().Done():
			pubsub.RobotCMDChanMap.Delete(chanMapKey)
			return nil
		case data := <-robotCMDChan:
			setRuningMapCMD := &robotc.SetRuningMapCMD{}
			dataBytes, err := base64.StdEncoding.DecodeString(data)
			if err != nil {
				break
			}
			proto.Unmarshal(dataBytes, setRuningMapCMD)
			if err := sess.Send(setRuningMapCMD); err != nil {
				return nil
			}
		}
	}
}

// 选择地图响应 SetRunningMapC2Srsp
func (s *Server) SetRunningMapC2Srsp(ctx context.Context, msg *robotc.RspSetRuningMap2ClientC2S) (*robotc.ReqSetRuningMap2ClientS2C, error) {
	dataBytes, _ := proto.Marshal(msg)
	pubsub.PublishCMDRsp(GetRobotID(ctx), "95", "setRunningMap", "0", dataBytes)
	return &robotc.ReqSetRuningMap2ClientS2C{NVoid: 1}, nil
}

// 地图采集模式命令封装
func SetCollectMode2ClientCMDPack(robotID string, mapName string, username string) error {
	// 参数检查
	if robotID == "" {
		return errorx.New("设备id不能为空", -1)
	}
	if mapName == "" {
		return errorx.New("地图名称不能为空", -1)
	}
	// 判断设备是否存在
	var count int64
	mysql.NewDB().
		Model(&models.Device{}).
		Where(models.Device{
			RobotID: robotID,
		}).Count(&count)
	if count <= 0 {
		return errorx.New("设备不存在", -1)
	}

	collectModeCMD := &robotc.CollectModeCMD{}
	collectModeCMD.StrMapName = mapName
	// 额外数据
	exData := &robotc.CmdExtend{}
	exData.LlTimeStamp = time.Now().UnixMilli()
	exData.StrUserName = []byte(username)
	collectModeCMD.ExData = exData
	dataBytes, err := proto.Marshal(collectModeCMD)
	if err != nil {
		logx.Error(context.Background(), "SetCollectMode2ClientCMDPack", logx.Err(err))
	}
	err = pubsub.CMDPublish(robotID, "95", "setCollectMode", base64.StdEncoding.EncodeToString(dataBytes))
	return err
}

// SetCollectMode2Client 进入地图采集模式
func (s *Server) SetCollectMode2Client(in *robotc.CollectModeCMDParam, rsp robotc.BaseService_SetCollectMode2ClientServer) error {
	robotID := GetRobotID(rsp.Context())
	chanMapKey := "setCollectMode@" + robotID + "@95"
	pubsub.RobotCMDChanMap.Store(chanMapKey, make(chan string, 1))
	defer pubsub.RobotCMDChanMap.Delete(chanMapKey)
	robotCMDChanInterface, _ := pubsub.RobotCMDChanMap.Load(chanMapKey)
	robotCMDChan, _ := robotCMDChanInterface.(chan string)
	// 订阅对应主题
	for {
		select {
		case <-rsp.Context().Done():
			pubsub.RobotCMDChanMap.Delete(chanMapKey)
			return nil
		case data := <-robotCMDChan:
			collectModeCMD := &robotc.CollectModeCMD{}
			dataBytes, err := base64.StdEncoding.DecodeString(data)
			if err != nil {
				break
			}
			proto.Unmarshal(dataBytes, collectModeCMD)
			if err := rsp.Send(collectModeCMD); err != nil {
				return nil
			}
		}
	}
}

// SetCollectModeFinish2ClientCMCPack 地图采集结束命令封装
func SetCollectModeFinish2ClientCMCPack(robotID string, username string) error {
	// 参数检查
	if robotID == "" {
		return errorx.New("设备id不能为空", -1)
	}
	// 判断设备是否存在
	var count int64
	mysql.NewDB().
		Model(&models.Device{}).
		Where(models.Device{
			RobotID: robotID,
		}).Count(&count)
	if count <= 0 {
		return errorx.New("设备不存在", -1)
	}

	cmd := &robotc.CollectModeFinishCMD{}
	cmd.BFinish = true
	// 额外数据
	exData := &robotc.CmdExtend{}
	exData.LlTimeStamp = time.Now().UnixMilli()
	exData.StrUserName = []byte(username)
	cmd.ExData = exData
	dataBytes, err := proto.Marshal(cmd)
	if err != nil {
		logx.Error(context.Background(), "SetCollectModeFinish2ClientCMCPack", logx.Err(err))
	}
	err = pubsub.CMDPublish(robotID, "95", "setCollectModeFinish", base64.StdEncoding.EncodeToString(dataBytes))
	return err
}

// SetCollectModeFinish2Client 地图采集结束
func (s *Server) SetCollectModeFinish2Client(in *robotc.CollectModeFinishCMDParam, rsp robotc.BaseService_SetCollectModeFinish2ClientServer) error {
	robotID := GetRobotID(rsp.Context())
	chanMapKey := "setCollectModeFinish@" + robotID + "@95"
	pubsub.RobotCMDChanMap.Store(chanMapKey, make(chan string, 1))
	defer pubsub.RobotCMDChanMap.Delete(chanMapKey)
	robotCMDChanInterface, _ := pubsub.RobotCMDChanMap.Load(chanMapKey)
	robotCMDChan, _ := robotCMDChanInterface.(chan string)
	// 订阅对应主题
	for {
		select {
		case <-rsp.Context().Done():
			pubsub.RobotCMDChanMap.Delete(chanMapKey)
			return nil
		case data := <-robotCMDChan:
			cmd := &robotc.CollectModeFinishCMD{}
			dataBytes, err := base64.StdEncoding.DecodeString(data)
			if err != nil {
				break
			}
			proto.Unmarshal(dataBytes, cmd)
			if err := rsp.Send(cmd); err != nil {
				return nil
			}
		}
	}
}

// ReqDetectLoopCMDPack 回环检测
func ReqDetectLoopCMDPack(robotID string) (stat int32, err error) {
	reqDetectLoop := &robotc.ReqDetectLoop{}
	wm := &robotc.WildMsg{}
	wm.FromType = robotc.WildMsg_eMobileServiceClient
	wm.FromID = "server"
	wm.ToType = robotc.WildMsg_eMonitorClient
	wm.ToID = "95"
	wm.BodyType = string(reqDetectLoop.ProtoReflect().Descriptor().FullName())
	wm.BodyContent, err = proto.Marshal(reqDetectLoop)
	if err != nil {
		logx.Error(context.Background(), "ReqDetectLoopCMDPack", logx.Err(err))
		return 0, err
	}
	wm.StrGUID = helper.RandString(16)
	bytes, err := proto.Marshal(wm)
	if err != nil {
		logx.Error(context.Background(), "ReqDetectLoopCMDPack", logx.Err(err))
		return 0, err
	}
	// 发布
	err = pubsub.CMDPublish(robotID, "95", "wildmsg", base64.StdEncoding.EncodeToString(bytes))
	if err != nil {
		return 0, err
	}
	// 等待命令执行结果
	resData, err := cmd.WaitCmdRsp(robotID, "wildmsg", wm.StrGUID, "95")
	if err != nil {
		return 0, err
	}
	// 解析响应的内容
	rspDetectLoop := &robotc.RspDetectLoop{}
	if err := proto.Unmarshal(resData, rspDetectLoop); err == nil {
		return rspDetectLoop.GetLoopStat(), nil
	}
	return 0, err
}

// rspDetectLoopHandle 回路检测响应处理
func rspDetectLoopHandle(ctx context.Context, msg *robotc.WildMsg) {
	// 命令序号
	// cmdSeq := msg.GetStrGUID()
	// 获取响应的结果
	pubsub.PublishCMDRsp(GetRobotID(ctx), "95", "reqDetectLoop", "0", msg.GetBodyContent())
}

// 修改任务次数命令封装
func ModifyWorkingTimesCMDPack(robotID string, mapName string, taskName string, runCount int, username string) error {
	cmd := &robotc.ModifyWorkingTimesCMD{
		ExData: &robotc.CmdExtend{},
	}
	cmd.StrMapName = []byte(mapName)
	cmd.StrTaskName = []byte(taskName)
	cmd.NRunCount = int32(runCount)
	cmd.ExData.LlTimeStamp = time.Now().UnixMilli()
	cmd.ExData.StrUserName = []byte(username)
	dataBytes, _ := proto.Marshal(cmd)
	err := pubsub.CMDPublish(robotID, "95", "modifyWorkingTimes", base64.StdEncoding.EncodeToString(dataBytes))
	return err
}

// 修改任务次数 ModifyWorkingTimes2Client
func (s *Server) ModifyWorkingTimes2Client(in *robotc.ModifyWorkingTimesCMDParam, rsp robotc.BaseService_ModifyWorkingTimes2ClientServer) error {
	robotID := GetRobotID(rsp.Context())
	chanMapKey := "modifyWorkingTimes@" + robotID + "@95"
	pubsub.RobotCMDChanMap.Store(chanMapKey, make(chan string, 1))
	defer pubsub.RobotCMDChanMap.Delete(chanMapKey)
	robotCMDChanInterface, _ := pubsub.RobotCMDChanMap.Load(chanMapKey)
	robotCMDChan, _ := robotCMDChanInterface.(chan string)
	logx.Info(context.Background(), "ModifyWorkingTimes2Client-connected", logx.String("robot_id", robotID))
	// 订阅对应主题
	for {
		select {
		case <-rsp.Context().Done():
			pubsub.RobotCMDChanMap.Delete(chanMapKey)
			logx.Info(context.Background(), "ModifyWorkingTimes2Client-disconnected", logx.String("robot_id", robotID))
			return nil
		case data := <-robotCMDChan:
			cmd := &robotc.ModifyWorkingTimesCMD{}
			dataBytes, err := base64.StdEncoding.DecodeString(data)
			if err != nil {
				break
			}
			proto.Unmarshal(dataBytes, cmd)
			if err := rsp.Send(cmd); err != nil {
				logx.Error(context.Background(), "ModifyWorkingTimes2Client-sendCMDErr", logx.String("robot_id", robotID))
				return nil
			}
			logx.Info(context.Background(), "ModifyWorkingTimes2Client-sendCMD", logx.String("robot_id", robotID))
		}
	}
}

// 修改任务时段命令封装
func ModifyWorkingTimeCMDPack(robotID string, mapName string, taskName string, startHour int, startMin int, endHour int, endMin int, username string) error {
	cmd := &robotc.ModifyWorkingTimeCMD{
		ExData: &robotc.CmdExtend{},
	}
	cmd.StrMapName = []byte(mapName)
	cmd.StrTaskName = []byte(taskName)
	cmd.NStartHour = int32(startHour)
	cmd.NStartMin = int32(startMin)
	cmd.NEndHour = int32(endHour)
	cmd.NEndMin = int32(endMin)
	cmd.ExData.LlTimeStamp = time.Now().UnixMilli()
	cmd.ExData.StrUserName = []byte(username)
	dataBytes, _ := proto.Marshal(cmd)
	err := pubsub.CMDPublish(robotID, "95", "modifyWorkingTime", base64.StdEncoding.EncodeToString(dataBytes))
	return err
}

// 修改任务时段 ModifyWorkingTime2Client
func (s *Server) ModifyWorkingTime2Client(in *robotc.ModifyWorkingTimeCMDParam, rsp robotc.BaseService_ModifyWorkingTime2ClientServer) error {
	robotID := GetRobotID(rsp.Context())
	chanMapKey := "modifyWorkingTime@" + robotID + "@95"
	pubsub.RobotCMDChanMap.Store(chanMapKey, make(chan string, 1))
	defer pubsub.RobotCMDChanMap.Delete(chanMapKey)
	robotCMDChanInterface, _ := pubsub.RobotCMDChanMap.Load(chanMapKey)
	robotCMDChan, _ := robotCMDChanInterface.(chan string)
	logx.Info(context.Background(), "ModifyWorkingTime2Client-connected", logx.String("robot_id", robotID))
	// 订阅对应主题
	for {
		select {
		case <-rsp.Context().Done():
			logx.Info(context.Background(), "ModifyWorkingTime2Client-disconnected", logx.String("robot_id", robotID))
			pubsub.RobotCMDChanMap.Delete(chanMapKey)
			return nil
		case data := <-robotCMDChan:
			cmd := &robotc.ModifyWorkingTimeCMD{}
			dataBytes, err := base64.StdEncoding.DecodeString(data)
			if err != nil {
				break
			}
			proto.Unmarshal(dataBytes, cmd)
			if err := rsp.Send(cmd); err != nil {
				logx.Info(context.Background(), "ModifyWorkingTime2Client-sendCMDErr", logx.String("robot_id", robotID))
				return nil
			}
			logx.Info(context.Background(), "ModifyWorkingTime2Client-sendCMD", logx.String("robot_id", robotID))
		}
	}
}

func (s *Server) ModifyWorkingTimesC2Srsp(ctx context.Context, data *robotc.RspModifyWorkingTimes2ClientC2S) (*robotc.ReqModifyWorkingTimes2ClientS2C, error) {
	if data.GetEnStatus() == 1 {
		cache.Set("ModifyWorkingTimesC2Srsp:"+GetRobotID(ctx), 1, time.Second*2, nil)
	}
	return &robotc.ReqModifyWorkingTimes2ClientS2C{NVoid: 1}, nil
}

func (s *Server) ModifyWorkingTimeC2Srsp(ctx context.Context, data *robotc.RspModifyWorkingTime2ClientC2S) (*robotc.ReqModifyWorkingTime2ClientS2C, error) {
	if data.GetEnStatus() == 1 {
		cache.Set("ModifyWorkingTimeC2Srsp:"+GetRobotID(ctx), 1, time.Second*2, nil)
	}
	return &robotc.ReqModifyWorkingTime2ClientS2C{NVoid: 1}, nil
}
